/**
 * @file CircBuffTest.cpp
 *
 * @author Luc Fourestier
 */

#include "CircBuffTest.h"
#include "cute.h"
#include "Global.h"
#include "CircBuff.h"

#include <pthread.h>

static pthread_t WaitThreadId;
static void * WaitForReadThread(void * Arg);
static void * WaitForWriteThread(void * Arg);
static int Status = 0;


#define ELT_ONE 0x11111111 // Needed to do a direct check
unsigned int TestBuffer[] =
{
	0x00000000, ELT_ONE, 0x22222222, 0x33333333, 0x44444444,
	0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999
};
#define TESTBUFF_MAX_ELT (sizeof(TestBuffer)/sizeof(unsigned int))

// Test functions

/**
 * @brief Test CircBuff creation/deletion
 *
 */
void CircBuffTest_CreateDelete()
{
#define ELT_SIZE 4
#define MAX_ELT 25

	CircBuff_t * TestCB_p = NULL;

	// Test creation
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);
	ASSERTM("TestCB_p->Buffer_p is NULL", TestCB_p->Buffer_p!=NULL);
	ASSERTM("TestCB_p->ElementStat is NULL", TestCB_p->ElementSize!=NULL);
	ASSERT_EQUALM("ReadIndex", 0, TestCB_p->ReadIndex);
	ASSERT_EQUALM("WriteIndex", 0, TestCB_p->WriteIndex);
	ASSERT_EQUALM("ElementSize", ELT_SIZE, TestCB_p->MaxElementSize);
	ASSERT_EQUALM("MaxElement", MAX_ELT, TestCB_p->MaxElementNumber);

	// Test deletion
	Error = CircB_DeleteCircBuff(TestCB_p);

	ASSERT_EQUALM("Error", OK, Error);

#undef ELT_SIZE
#undef MAX_ELT
}

/**
 * @brief Test read and write
 */
void CircBuffTest_ReadWrite()
{
#define ELT_SIZE sizeof(unsigned int)
#define MAX_ELT TESTBUFF_MAX_ELT+13

	CircBuff_t * TestCB_p = NULL;
	unsigned int ReadValue = 0;
	unsigned int Size = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Test read and write once on ELT_ONE and check Indexes
	Size = ELT_SIZE;
	Error = CircB_Write(TestCB_p, &TestBuffer[1], &Size, CIRCB_WAIT_FOREVER);
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("Size", ELT_SIZE, Size);

	Size = sizeof(ReadValue);
	Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_WAIT_FOREVER);
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("Size", ELT_SIZE, Size);

	ASSERT_EQUALM("Indexes", 1, TestCB_p->WriteIndex);
	ASSERT_EQUALM("Indexes", 1, TestCB_p->ReadIndex);
	ASSERT_EQUALM("Read value", ELT_ONE, ReadValue);

	// Test read and write several times and check values equality
	for (unsigned int i=0; i<TESTBUFF_MAX_ELT; i++)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_WAIT_FOREVER);
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Size", ELT_SIZE, Size);

		Size = sizeof(ReadValue);
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_WAIT_FOREVER);
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Size", ELT_SIZE, Size);

		ASSERT_EQUALM("Read value", TestBuffer[i], ReadValue);
	}

	ASSERT_EQUALM("Indexes equality", TestCB_p->ReadIndex, TestCB_p->WriteIndex);

	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT

}

/**
 * @brief Test read and write with another element size
 */
void CircBuffTest_ReadWriteOtherEltSize()
{
#define ELT_SIZE sizeof(unsigned long long)
#define MAX_ELT TESTBUFF_MAX_ELT+13

	CircBuff_t * TestCB_p = NULL;
	unsigned long long ReadValue = 0;
	unsigned int Size = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Test read and write several times and check values equality
	for (unsigned int i=0; i<(TESTBUFF_MAX_ELT/2); i++)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[2*i], &Size, CIRCB_WAIT_FOREVER);
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Size", ELT_SIZE, Size);

		Size = sizeof(ReadValue);
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_WAIT_FOREVER);
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Size", ELT_SIZE, Size);

		ASSERT_EQUALM("Read value", *(unsigned long long*)(TestBuffer+2*i), ReadValue);
	}

	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT

}

/**
 * @brief Test buffer full and empty
 */
void CircBuffTest_FullnessEmptyness()
{
#define ELT_SIZE sizeof(unsigned int)
#define MAX_ELT TESTBUFF_MAX_ELT+13

	CircBuff_t * TestCB_p = NULL;
	unsigned int i = 0;
	unsigned int ReadValue = 0;
	unsigned int Size = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Test fullness from scratch
	Error = OK;
	i = 0;
	while (Error != CIRCB_ERROR_BUFFER_FULL)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		i++;
		i %= TESTBUFF_MAX_ELT;
	}

	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_FULL, Error);
	ASSERT_EQUALM("Indexes", 0, TestCB_p->WriteIndex);
	ASSERT_EQUALM("Indexes", 0, TestCB_p->ReadIndex);

	// Test emptyness from scratch
	Error = OK;
	while (Error != CIRCB_ERROR_BUFFER_EMPTY)
	{
		Size = sizeof(ReadValue);
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_NO_WAIT);
	}

	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_EMPTY, Error);
	ASSERT_EQUALM("Indexes", 0, TestCB_p->ReadIndex);

	// Play a bit with the circular buffer (to shuffle the indexes)
	for (i=0; i<TESTBUFF_MAX_ELT; i++)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		ASSERT_EQUALM("Error", OK, Error);

		Size = sizeof(ReadValue);
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_NO_WAIT);
		ASSERT_EQUALM("Error", OK, Error);
	}

	// Test fullness
	Error = OK;
	i = 0;
	while (Error != CIRCB_ERROR_BUFFER_FULL)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		i++;
		i %= TESTBUFF_MAX_ELT;
	}

	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_FULL, Error);
	ASSERT_EQUALM("Indexes equality", TestCB_p->ReadIndex, TestCB_p->WriteIndex);

	// Test emptyness
	Error = OK;
	while (Error != CIRCB_ERROR_BUFFER_EMPTY)
	{
		Size = sizeof(ReadValue);
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_NO_WAIT);
	}

	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_EMPTY, Error);
	ASSERT_EQUALM("Indexes equality", TestCB_p->ReadIndex, TestCB_p->WriteIndex);

	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT

}

/**
 * @brief Test read without copy
 */
void CircBuffTest_ReadNoCopy()
{
#define ELT_SIZE sizeof(unsigned int)
#define MAX_ELT TESTBUFF_MAX_ELT+13

	CircBuff_t * TestCB_p = NULL;
	unsigned int * ReadValue_p = NULL;
	unsigned int ReadValue = 0;
	unsigned int i = 0;
	unsigned int Size = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Fill the circular buffer with values
	for (i=0; i<TESTBUFF_MAX_ELT; i++)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		ASSERT_EQUALM("Error", OK, Error);
	}

	// Test read no copy once
	Error = CircB_ReadNoCopy(TestCB_p, (void**)&ReadValue_p, &Size, CIRCB_NO_WAIT);
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("Size", ELT_SIZE, Size);
	ASSERT_EQUALM("Indexes", 0, TestCB_p->ReadIndex);
	ASSERT_EQUALM("Read value", TestBuffer[0], *ReadValue_p);

	Error = CircB_IncrementReadIndex(TestCB_p);
	ASSERT_EQUALM("Error", OK, Error);

	Error = CircB_UnlockRead(TestCB_p);
	ASSERT_EQUALM("Error", OK, Error);

	// Test that it accept NULL pointer as Size
	Error = CircB_ReadNoCopy(TestCB_p, (void**)&ReadValue_p, NULL, CIRCB_NO_WAIT);
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("Read value", TestBuffer[1], *ReadValue_p);

	Error = CircB_IncrementReadIndex(TestCB_p);
	ASSERT_EQUALM("Error", OK, Error);

	Error = CircB_UnlockRead(TestCB_p);
	ASSERT_EQUALM("Error", OK, Error);

	// Test read no copy with incrementation
	for (i=2; i<TESTBUFF_MAX_ELT; i++)
	{
		Error = CircB_ReadNoCopy(TestCB_p, (void**)&ReadValue_p, &Size, CIRCB_NO_WAIT);
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Size", ELT_SIZE, Size);
		ASSERT_EQUALM("Indexes", i, TestCB_p->ReadIndex);
		ASSERT_EQUALM("Read value", TestBuffer[i], *ReadValue_p);

		Error = CircB_IncrementReadIndex(TestCB_p);
		ASSERT_EQUALM("Error", OK, Error);

		Error = CircB_UnlockRead(TestCB_p);
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Indexes", i+1, TestCB_p->ReadIndex);
	}

	// Test read no copy when buffer empty
	Error = OK;
	while (Error != CIRCB_ERROR_BUFFER_EMPTY)
	{
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_NO_WAIT);
	}

	Error = CircB_ReadNoCopy(TestCB_p, (void**)&ReadValue_p, &Size, CIRCB_NO_WAIT);
	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_EMPTY, Error);
	ASSERT_EQUALM("Size", 0, Size);

	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT

}

/**
 * @brief Test read without copy
 */
void CircBuffTest_Watch()
{
#define ELT_SIZE sizeof(unsigned int)
#define MAX_ELT TESTBUFF_MAX_ELT+13

	CircBuff_t * TestCB_p = NULL;
	unsigned int * ReadValue_p = NULL;
	unsigned int ReadValue = 0;
	unsigned int i = 0;
	unsigned int Size = 0;
	unsigned int Index = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Fill the circular buffer with values
	for (i=0; i<TESTBUFF_MAX_ELT; i++)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		ASSERT_EQUALM("Error", OK, Error);
	}

	// Test watch once
	Index = TestCB_p->ReadIndex;
	Error = CircB_Watch(TestCB_p, Index, (void**)&ReadValue_p, &Size);
	if ((Error == CIRCB_WARNING_END_OF_BUFF) || (Error == CIRCB_WARNING_BEG_OF_BUFF))
	{
		Error = OK;
	}
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("Size", ELT_SIZE, Size);
	ASSERT_EQUALM("Indexes", 0, Index);
	ASSERT_EQUALM("Read value", TestBuffer[0], *ReadValue_p);

	// Test that index does not move and that it accept NULL pointer as Size
	Error = CircB_Watch(TestCB_p, Index, (void**)&ReadValue_p, &Size);
	if ((Error == CIRCB_WARNING_END_OF_BUFF) || (Error == CIRCB_WARNING_BEG_OF_BUFF))
	{
		Error = OK;
	}
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("Read value", TestBuffer[0], *ReadValue_p);

	// Test watch with incrementation
	Index = 0;
	for (i=0; i<TESTBUFF_MAX_ELT; i++)
	{
		Error = CircB_Watch(TestCB_p, Index, (void**)&ReadValue_p, &Size);
		if ((Error == CIRCB_WARNING_END_OF_BUFF) || (Error == CIRCB_WARNING_BEG_OF_BUFF))
		{
			Error = OK;
		}
		ASSERT_EQUALM("Error", OK, Error);
		ASSERT_EQUALM("Size", ELT_SIZE, Size);
		ASSERT_EQUALM("Read value", TestBuffer[i], *ReadValue_p);

		Index++;
		Index %= TestCB_p->MaxElementNumber;
	}

	// Test watch when buffer empty
	Error = OK;
	while (Error != CIRCB_ERROR_BUFFER_EMPTY)
	{
		Error = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_NO_WAIT);
	}

	Error = CircB_Watch(TestCB_p, Index, (void**)&ReadValue_p, &Size);
	if ((Error == CIRCB_WARNING_END_OF_BUFF) || (Error == CIRCB_WARNING_BEG_OF_BUFF))
	{
		Error = OK;
	}
	ASSERT_EQUALM("Error", CIRCB_ERROR_ELT_EMPTY, Error);
	ASSERT_EQUALM("Size", 0, Size);
	ASSERTM("Read value is not NULL", ReadValue_p == NULL);

	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT

}

/**
 * @brief Test Wait read
 */
void CircBuffTest_WaitRead()
{
#define ELT_SIZE sizeof(unsigned int)
#define MAX_ELT TESTBUFF_MAX_ELT+13
#define WAIT_TIME 1

	CircBuff_t * TestCB_p = NULL;
	unsigned int i = 0;
	unsigned int Size = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Test wait for read with KillWait
	Status = OK;
	Error = pthread_create(&WaitThreadId, NULL, WaitForReadThread, (void*) TestCB_p);
	ASSERT_EQUALM("Error", 0, Error);

	printf("Waiting %i seconds...\n", WAIT_TIME);
	sleep(WAIT_TIME);

	Error = CircB_KillRead(TestCB_p);
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("TestCB_p->KillRead", 1, TestCB_p->KillRead);

	pthread_join(WaitThreadId, NULL);
	ASSERT_EQUALM("Status", CIRCB_ERROR_WAIT_KILLED, Status);

	// Test wait with filled buffer
	Error = OK;
	i = 0;
	while (Error != CIRCB_ERROR_BUFFER_FULL)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		i++;
		i %= TESTBUFF_MAX_ELT;
	}

	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_FULL, Error);

	Status = OK;
	Error = pthread_create(&WaitThreadId, NULL, WaitForReadThread, (void*) TestCB_p);
	ASSERT_EQUALM("Error", 0, Error);

	printf("Waiting %i seconds...\n", WAIT_TIME);
	sleep(WAIT_TIME);

	pthread_join(WaitThreadId, NULL);
	ASSERT_EQUALM("Status", OK, Status);


	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT
#undef WAIT_TIME
}

static void * WaitForReadThread(void * Arg)
{
	CircBuff_t * TestCB_p = (CircBuff_t *)Arg;
	unsigned int ReadValue = 0;
	unsigned int Size = sizeof(ReadValue);

	Status = CircB_Read(TestCB_p, &ReadValue, &Size, CIRCB_WAIT_FOREVER);

	pthread_exit((void*)&Status);
}

/**
 * @brief Test Wait write
 */
void CircBuffTest_WaitWrite()
{
#define ELT_SIZE sizeof(unsigned int)
#define MAX_ELT TESTBUFF_MAX_ELT+13
#define WAIT_TIME 1

	CircBuff_t * TestCB_p = NULL;
	unsigned int i = 0;
	unsigned int Size = 0;

	// Create
	int Error = CircB_CreateCircBuff(&TestCB_p, ELT_SIZE, MAX_ELT);

	ASSERT_EQUALM("Error", OK, Error);
	ASSERTM("TestCB_p is NULL", TestCB_p!=NULL);

	// Test wait for write with empty buffer
	Status = OK;
	Error = pthread_create(&WaitThreadId, NULL, WaitForWriteThread, (void*) TestCB_p);
	ASSERT_EQUALM("Error", 0, Error);

	printf("Waiting %i seconds...\n", WAIT_TIME);
	sleep(WAIT_TIME);

	pthread_join(WaitThreadId, NULL);
	ASSERT_EQUALM("Status", OK, Status);

	// Test wait with filled buffer
	Error = OK;
	i = 0;
	while (Error != CIRCB_ERROR_BUFFER_FULL)
	{
		Size = ELT_SIZE;
		Error = CircB_Write(TestCB_p, &TestBuffer[i], &Size, CIRCB_NO_WAIT);
		i++;
		i %= TESTBUFF_MAX_ELT;
	}

	ASSERT_EQUALM("Error", CIRCB_ERROR_BUFFER_FULL, Error);

	Status = OK;
	Error = pthread_create(&WaitThreadId, NULL, WaitForWriteThread, (void*) TestCB_p);
	ASSERT_EQUALM("Error", 0, Error);

	printf("Waiting %i seconds...\n", WAIT_TIME);
	sleep(WAIT_TIME);

	Error = CircB_KillWrite(TestCB_p);
	ASSERT_EQUALM("Error", OK, Error);
	ASSERT_EQUALM("TestCB_p->KillWrite", 1, TestCB_p->KillWrite);

	pthread_join(WaitThreadId, NULL);
	ASSERT_EQUALM("Status", CIRCB_ERROR_WAIT_KILLED, Status);


	// Delete
	CircB_DeleteCircBuff(TestCB_p);

#undef ELT_SIZE
#undef MAX_ELT
#undef WAIT_TIME
}

static void * WaitForWriteThread(void * Arg)
{
	CircBuff_t * TestCB_p = (CircBuff_t *)Arg;
	unsigned int WriteValue = 0x55;
	unsigned int Size = sizeof(WriteValue);

	Status = CircB_Write(TestCB_p, &WriteValue, &Size, CIRCB_WAIT_FOREVER);

	pthread_exit((void*)&Status);
}

